package cn.flying.cloud.auth.security.configuration;

import javax.annotation.Resource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import cn.flying.cloud.auth.security.captcha.CaptchaAuthenticationProvider;
import cn.flying.cloud.auth.security.captcha.CaptchaWebAuthenticationDetailsSource;
import cn.flying.cloud.auth.security.details.CustomUserDetailService;
import cn.flying.cloud.auth.security.filter.JwtPreAuthenticationFilter;
import cn.flying.cloud.auth.security.handler.CustomAccessDeniedHandler;
import cn.flying.cloud.auth.security.handler.CustomFailureHandler;
import cn.flying.cloud.auth.security.handler.CustomLogoutSuccessHandler;
import cn.flying.cloud.auth.security.handler.CustomSuccessHandler;
import cn.flying.cloud.auth.security.handler.CustomUnauthorizedEntryPointHandler;
import cn.flying.cloud.base.redis.service.RedisService;

/**
 * security 配置类
 *
 * @author: admin
 * @date: 2022年11月07日 20:36
 * @version: 1.0
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
//@EnableGlobalAuthentication
@EnableWebSecurity(debug = false)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Resource
    private CustomUserDetailService customUserDetailService;
    @Resource
    private CustomSuccessHandler customSuccessHandler;
    @Resource
    private CustomFailureHandler customFailureHandler;
    @Resource
    private CustomUnauthorizedEntryPointHandler customUnauthorizedEntryPointHandler;
    @Resource
    private CustomAccessDeniedHandler customAccessDeniedHandler;
    @Resource
    private CustomLogoutSuccessHandler customLogoutSuccessHandler;
    @Resource
    private CaptchaWebAuthenticationDetailsSource captchaWebAuthenticationDetailsSource;
    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private RedisService redisService;

    /**
     * 定制基于 HTTP 请求的用户访问控制
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //添加token过滤验证
        http.addFilterBefore(jwtPreAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        //开启表单验证
        http.formLogin()
                //登陆请求的url地址，自定义即可
                .loginProcessingUrl("/auth/user/login")
                //设置登录验证成功认证成功处理器
                .successHandler(customSuccessHandler)
                //设置登录验证成功认证失败处理器
                .failureHandler(customFailureHandler)
                //指定authenticationDetailsSource
                .authenticationDetailsSource(captchaWebAuthenticationDetailsSource);

        //请求访问控制，设置需要拦截的请求
        http.authorizeRequests()
                //放行请求，这里的所有请求必须是post提交
                .antMatchers("/auth/user/login", "/auth/user/register", "/auth/user/reset", "/auth/user/refresh").permitAll()
                //其他的请求都需要进行身份认证
                .anyRequest().authenticated()
                //对于所有的请求都需要采用该方法校验是否有权限
//                .anyRequest().access("@rbacService.hasPermission(request, authentication)")
                .and()
                .exceptionHandling()
                //匿名无权限访问
                .authenticationEntryPoint(customUnauthorizedEntryPointHandler)
                //认证用户无权限访问
                .accessDeniedHandler(customAccessDeniedHandler);

        //禁用匿名登录
//        http.anonymous().disable();

        //session管理配置，这里禁用session
        http.sessionManagement()
                //是session的生成策略，前后端分离的时候配置成STATELESS，非前后端分离的需要配置成ALWAYS，即禁用session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //登出配置
        http.logout()
                .logoutUrl("/auth/user/logout")
                .logoutSuccessHandler(customLogoutSuccessHandler)
                // 指定用户注销登录时删除的 Cookie
                .deleteCookies("JSESSIONID")
                //用户注销登录时是否立即清除用户的 Session，默认为 true
                .invalidateHttpSession(true)
                // 用户注销登录时是否立即清除用户认证信息 Authentication，默认为 true
                .clearAuthentication(true);

        //开启跨域配置，禁用csrf防御机制,是禁用使用跨站请求,可解决POST请求403问题
        http.cors().and().csrf().disable().headers().frameOptions().disable();
    }

    /**
     * 配置忽略资源
     * 一般是配置系统静态资源用，配置的请求根本不会进入 spring security 的过滤器链，直接放行
     * .antMatchers("/").permitAll() 是会进入 spring security 的过滤器链的，这是 2 者的主要区别
     * /login（登录）、/captcha（获取图片验证码）、/forget（忘记密码）、/register（注册）、/error（错误页面）
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers(
//                "/auth/user/forget",
                "/auth/user/captcha",
                "/auth/user/send/code",
                "/auth/user/valid/code",
                "/druid/**"

        );
    }

    /**
     * 定制用户认证管理器来实现用户认证，测试可以采用内存inMemoryAuthentication
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 指定UserDetailService和加密器，不再使用内存方式存储用户认证信息，而是动态从数据库中获取
        auth.userDetailsService(customUserDetailService).passwordEncoder(passwordEncoder);
    }

    /**
     * 初始化一个自定义的校验provider
     *
     * @return
     */
    @Bean
    public CaptchaAuthenticationProvider customAuthenticationProvider() {
        CaptchaAuthenticationProvider provider = new CaptchaAuthenticationProvider();
        provider.setPasswordEncoder(passwordEncoder);
        provider.setUserDetailsService(customUserDetailService);
        //修改属性，当用户名查不到时可以明确抛出UsernameNotFoundException ，而不是抛出BadCredentialsException
        //具体可以查看AbstractUserDetailsAuthenticationProvider类的认证逻辑
        provider.setHideUserNotFoundExceptions(false);
        return provider;
    }

    /**
     * 认证管理器添加自定义provider
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    protected AuthenticationManager authenticationManager() {
        return new ProviderManager(customAuthenticationProvider());
    }

    /**
     * Jwt过滤器，拦截校验所有请求携带的Token
     *
     * @return
     */
    @Bean
    public JwtPreAuthenticationFilter jwtPreAuthenticationFilter() {
        JwtPreAuthenticationFilter filter = new JwtPreAuthenticationFilter(authenticationManager());
        filter.setUnauthorizedEntryPointHandler(customUnauthorizedEntryPointHandler);
        filter.setLoginUserDetailService(customUserDetailService);
        filter.setRedisService(redisService);
        return filter;
    }
}
